using GameFrame.Deterministic;
using GameFrame.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace GameFrame.Allocator
{
  public struct Heap
  {
    public const int FIELD_SIZE = 4;
    public const int DE_BRUIJN_TABLE_COUNT = 32;
    public const int DE_BRUIJN_TABLE_BYTE_SIZE = 32;
    public const int LOB_TABLE_DEFAULT_CAPACITY = 16;
    public const int WORD_SIZE = 8;
    public const int PAGE_BIN_COUNT = 49;
    public const int PAGE_BIN_MIN = 0;
    public const int PAGE_BIN_INVALID = -1;
    internal const int PAGE_BIN_MAX_ALLOC = 65536;
    public const int ALLOC_BIN_EXPANSION = 4;
    private Heap.Config _config;
    private Heap.Offsets _offsets;
    private byte _reserved;
    private unsafe byte* _memory;
    private unsafe Heap.Data* _data;
    private unsafe Heap.Bin* _bins;
    private unsafe Heap.Bin* _binsFull;
    private unsafe int* _binsSmall;
    private unsafe Heap.Page* _pages;
    private unsafe byte* _debruijnTable;
    private const int PTR_CHECK_MIN = 0;
    private const int PTR_CHECK_MAX = 0;
    private const string ENCODE_WRONG_POINTER = "Trying to convert a native pointer that lies outside of the heap.";
    private const string DECODE_WRONG_POINTER = "Trying to resolve a Ptr that lies outside of the valid range";
    private const string DECODE_WRONG_POINTER_SMALL = "Trying to resolve a Ptr that lies outside of the valid range, offset is smaller than valid.";
    private const string DECODE_WRONG_POINTER_LARGE = "Trying to resolve a Ptr that lies outside of the valid range, offset is larger than valid.";

    public unsafe Ptr AllocatePtr(int size)
    {
      void* ptr = this.Allocate(size);
      return (IntPtr) ptr != IntPtr.Zero ? this.VoidUnchecked(ptr) : Ptr.Null;
    }

    public unsafe Ptr AllocateAndClearPtr(int size)
    {
      void* ptr = this.AllocateAndClear(size);
      return (IntPtr) ptr != IntPtr.Zero ? this.VoidUnchecked(ptr) : Ptr.Null;
    }

    public unsafe void* AllocateAndClear(int size)
    {
      void* dest = this.Allocate(size);
      if ((IntPtr) dest != IntPtr.Zero)
        Native.Utils.Clear(dest, size);
      return dest;
    }

    public unsafe T* ExpandArray<T>(T* buffer, int currentSize, int newSize) where T : unmanaged
    {
      T* objPtr = buffer;
      T* dest = (T*) this.AllocateAndClear(sizeof (T) * newSize);
      Native.Utils.Copy((void*) dest, (void*) objPtr, sizeof (T) * currentSize);
      this.Free((void*) objPtr);
      return dest;
    }

    public unsafe void* Expand(void* buffer, int currentSize, int newSize)
    {
      void* voidPtr = buffer;
      void* dest = this.AllocateAndClear(newSize);
      Native.Utils.Copy(dest, voidPtr, currentSize);
      this.Free(voidPtr);
      return dest;
    }

    public unsafe void* Allocate(int size)
    {
      if (size <= 0)
        return (void*) null;
      if (size > 65536 || size > this._config.PAGE_SIZE)
      {
        int num = Math.Min(65536, this._config.PAGE_SIZE);
        throw new ArgumentException(string.Format("Allocation too large: {0} bytes (max: {1} bytes)", (object) size, (object) num), nameof (size));
      }
      int bin = size > this._config.SMALL_BIN_SIZE ? this.CalculateBin(size) : this._binsSmall[Heap.CalculateWordSize(size)];
      Heap.Bin* b = this._bins + bin;
      Heap.Page* p = (Heap.Page*) null;
      if (b->Head.Offset != 0)
      {
        p = (Heap.Page*) this.VoidUnchecked(b->Head);
        if (p->BlockFree.Offset != 0)
          return (void*) this.PageNextBlock(p, size, false);
      }
      return this.AllocateSlow(b, p, size, bin, 0);
    }

    private unsafe void* AllocateSlow(Heap.Bin* b, Heap.Page* p, int size, int bin, int tries)
    {
      if (tries == 3)
        return (void*) null;
      if ((IntPtr) p != IntPtr.Zero)
      {
        if (p->Committed < p->Reserved)
        {
          this.PageExpand(p);
          return (void*) this.PageNextBlockMustSucceed(p, size, false);
        }
        this.PageMoveToFullBin(p, bin);
        p = (Heap.Page*) null;
      }
      Ptr p1 = b->Head;
      while (p1.Offset != 0)
      {
        p = (Heap.Page*) this.VoidUnchecked(p1);
        p1 = p->Next;
        if (p->BlockFree.Offset != 0)
          return (void*) this.PageNextBlockMustSucceed(p, size, false);
        if (p->Committed < p->Reserved)
        {
          this.PageExpand(p);
          return (void*) this.PageNextBlockMustSucceed(p, size, false);
        }
        this.PageMoveToFullBin(p, bin);
      }
      Ptr p2 = this._binsFull[bin].Head;
      while (p2.Offset != 0)
      {
        p = (Heap.Page*) this.VoidUnchecked(p2);
        p2 = p->Next;
        if (p->BlockFree.Offset != 0)
        {
          Heap.Block* blockPtr = this.PageNextBlockMustSucceed(p, size, true);
          if (p->BlockFree.Offset != 0)
            this.PageMoveToAvailableBin(p, bin);
          return (void*) blockPtr;
        }
      }
      p = this.PageAllocate(bin);
      if ((IntPtr) p != IntPtr.Zero)
        return (void*) this.PageNextBlockMustSucceed(p, size, false);
      if (tries < 4)
      {
        int bin1 = bin + 1;
        if (bin1 < 49)
          return this.AllocateSlow(this._bins + bin1, (Heap.Page*) null, size, bin1, tries + 1);
      }
      return (void*) null;
    }

    private unsafe void BinInsertFirst(Heap.Bin* b, Heap.Page* p)
    {
      Ptr ptr = this.VoidUnchecked((void*) p);
      p->Next = b->Head;
      p->Prev = new Ptr();
      if (b->Head.Offset != 0)
      {
        ((Heap.Page*) this.VoidUnchecked(b->Head))->Prev = ptr;
        b->Head = ptr;
      }
      else
      {
        b->Head = ptr;
        b->Tail = ptr;
      }
      ++b->PageCount;
    }

    private unsafe void BinInsertLast(Heap.Bin* b, Heap.Page* p)
    {
      Ptr ptr = this.VoidUnchecked((void*) p);
      p->Next = new Ptr();
      p->Prev = b->Tail;
      if (b->Tail.Offset != 0)
      {
        ((Heap.Page*) this.VoidUnchecked(b->Tail))->Next = ptr;
        b->Tail = ptr;
      }
      else
      {
        b->Head = ptr;
        b->Tail = ptr;
      }
      ++b->PageCount;
    }

    private unsafe void BinRemove(Heap.Bin* b, Heap.Page* p)
    {
      if (p->Prev.Offset != 0)
        ((Heap.Page*) this.VoidUnchecked(p->Prev))->Next = p->Next;
      if (p->Next.Offset != 0)
        ((Heap.Page*) this.VoidUnchecked(p->Next))->Prev = p->Prev;
      Ptr ptr = this.VoidUnchecked((void*) p);
      if (ptr.Offset == b->Tail.Offset)
        b->Tail = p->Prev;
      if (ptr.Offset == b->Head.Offset)
        b->Head = p->Next;
      p->Prev.Offset = 0;
      p->Next.Offset = 0;
      --b->PageCount;
    }

    private unsafe bool BinContainsPage(Heap.Bin* b, Heap.Page* p)
    {
      Ptr ptr = this.VoidUnchecked((void*) p);
      for (Ptr p1 = b->Head; p1.Offset != 0; p1 = ((Heap.Page*) this.VoidUnchecked(p1))->Next)
      {
        if (p1.Offset == ptr.Offset)
          return true;
      }
      return false;
    }

    public bool Reserved
    {
      get => this._reserved == (byte) 1;
      set => this._reserved = value ? (byte) 1 : (byte) 0;
    }

    public unsafe byte* Memory => this._memory;

    public int Size => this._config.HEAP_SIZE;

    public static unsafe byte[] Blit(Heap* heap)
    {
      List<byte[]> numArrayList = new List<byte[]>(1);
      byte[] numArray = new byte[heap->Size];
      fixed (byte* dest = numArray)
        Native.Utils.Copy((void*) dest, (void*) heap->Memory, numArray.Length);
      numArrayList.Add(numArray);
      return ByteUtils.PackByteBlocks(numArrayList.ToArray());
    }

    public static unsafe void Blit(Heap* heap, byte[] byteBlocks, Native.Allocator allocator)
    {
      byte[][] array = ByteUtils.ReadByteBlocks(byteBlocks).ToArray<byte[]>();
      fixed (byte* src = array[0])
        Native.Utils.Copy((void*) heap->Memory, (void*) src, heap->Size);
      ((IEnumerable<byte[]>) array).Skip<byte[]>(1).ToArray<byte[]>();
    }

    public static unsafe ulong CRC(ulong crc, Heap* heap)
    {
      for (int index = 0; index < heap->_config.PAGE_COUNT; ++index)
      {
        if (heap->_pages[index].Allocated > 0)
          crc = CRC64.Calculate(crc, (void*) heap->PageMemoryStart(index), heap->_config.PAGE_SIZE);
      }
      return crc;
    }

    public static unsafe Heap.Stats GetStats(Heap* heap)
    {
      Heap.Stats stats = new Heap.Stats();
      stats.TotalMemory = heap->_config.HEAP_SIZE;
      stats.TotalPages = heap->_config.PAGE_COUNT;
      stats.PagesFree = heap->_data->PagesFreeCount;
      stats.PagesUsed = heap->_config.PAGE_COUNT - heap->_data->PagesFreeCount;
      for (int index = 0; index < heap->_config.PAGE_COUNT; ++index)
      {
        Heap.Page* pagePtr = heap->_pages + index;
        if ((pagePtr->Flags & Heap.PageFlags.Full) == Heap.PageFlags.Full)
          ++stats.PagesFull;
        if (pagePtr->Bin != -1 || (pagePtr->Flags & Heap.PageFlags.Meta) == Heap.PageFlags.Meta)
        {
          stats.BytesAllocated += pagePtr->BlockSize * pagePtr->Allocated;
          stats.BytesCommited += pagePtr->BlockSize * pagePtr->Committed;
          stats.BytesReserved += pagePtr->BlockSize * pagePtr->Reserved;
        }
      }
      return stats;
    }

    public static unsafe void Copy(Native.Allocator allocator, Heap* to, Heap* from)
    {
      if (from->_config != to->_config)
        throw new InvalidOperationException();
      for (int index = 0; index < from->_config.PAGE_COUNT; ++index)
      {
        if (from->_pages[index].Reserved > 0)
          Native.Utils.Copy((void*) to->PageMemoryStart(index), (void*) from->PageMemoryStart(index), from->_config.PAGE_SIZE);
      }
    }

    public static unsafe void Print(Heap* heap, FramePrinter printer)
    {
      printer.ScopeBegin();
      printer.AddLine("Config: " + heap->_config.ToString());
      printer.AddLine("Offset: " + heap->_offsets.ToString());
      printer.AddLine("Data: " + heap->_data->ToString());
      printer.AddLine("Bins:");
      printer.ScopeBegin();
      for (int index = 0; index < 49; ++index)
        printer.AddLine(string.Format("{0}: {1}", (object) index, (object) (heap->_bins + index)->ToString()));
      printer.ScopeEnd();
      printer.AddLine("BinsFull:");
      printer.ScopeBegin();
      for (int index = 0; index < 49; ++index)
        printer.AddLine(string.Format("{0}: {1}", (object) index, (object) (heap->_binsFull + index)->ToString()));
      printer.ScopeEnd();
      printer.AddLine("Pages:");
      printer.ScopeBegin();
      for (int index = 0; index < heap->_config.PAGE_COUNT; ++index)
      {
        printer.AddLine(string.Format("Page {0}:", (object) index));
        printer.ScopeBegin();
        printer.AddLine(string.Format("Offset: {0}", (object) (index * heap->_config.PAGE_SIZE)));
        printer.AddLine("Info: " + (heap->_pages + index)->ToString());
        printer.LineBegin("Free Block List: [");
        for (Ptr p = heap->_pages[index].BlockFree; p != Ptr.Null; p = ((Heap.Block*) heap->VoidUnchecked(p))->Next)
          printer.LineWrite(" " + p.ToString());
        printer.LineEnd("]");
        printer.AddLine("Dump: ");
        printer.ScopeBegin();
        UnmanagedUtils.PrintBytesHex(heap->PageMemoryStart(index), heap->_config.PAGE_SIZE, 256, printer);
        printer.ScopeEnd();
        printer.ScopeEnd();
      }
      printer.ScopeEnd();
      printer.ScopeEnd();
    }

    public static void RegisterInternalTypes(Action<Type, int> register)
    {
      register(typeof (Heap.Bin), 16);
      register(typeof (Heap.Data), 8);
      register(typeof (Heap.Page), 40);
      register(typeof (Heap.Block), 4);
      register(typeof (Heap.AllocationTracker), 8);
      register(typeof (FrameMetaData), 40);
      register(typeof (FrameMetaData.QHashCollectionList), 16);
    }

    private static unsafe Heap Initialize(
      byte* memory,
      byte* debruijnTable,
      Heap.Config config,
      Heap.Offsets offsets,
      Heap.Bin[] defaultBins)
    {
      Native.Utils.Clear((void*) memory, config.HEAP_SIZE);
      Heap heap;
      heap._reserved = (byte) 0;
      heap._config = config;
      heap._offsets = offsets;
      heap._debruijnTable = debruijnTable;
      heap._memory = memory;
      heap._data = (Heap.Data*) (heap._memory + offsets.DataOffset);
      heap._pages = (Heap.Page*) (heap._memory + offsets.PagesOffset);
      heap._bins = (Heap.Bin*) (heap._memory + offsets.BinsOffset);
      heap._binsFull = (Heap.Bin*) (heap._memory + offsets.BinsFullOffset);
      heap._binsSmall = (int*) (heap._memory + offsets.BinsSmallOffset);
      heap._data->PagesFreeCount = config.PAGE_COUNT - offsets.MetaDataPages;
      for (int index = config.PAGE_COUNT - 1; index >= 0; --index)
      {
        heap._pages[index].Index = index;
        heap._pages[index].Next = heap._data->PagesFree;
        heap._pages[index].Bin = -1;
        if (index >= offsets.MetaDataPages)
        {
          heap._data->PagesFree = heap.VoidUnchecked((void*) (heap._pages + index));
        }
        else
        {
          heap._pages[index].Flags = Heap.PageFlags.Full | Heap.PageFlags.Meta;
          heap._pages[index].Reserved = 1;
          heap._pages[index].Committed = 1;
          heap._pages[index].Allocated = 1;
          heap._pages[index].BlockSize = heap._config.PAGE_SIZE;
        }
      }
      for (int index = 0; index < 49; ++index)
      {
        heap._binsFull[index] = defaultBins[index];
        heap._bins[index] = defaultBins[index];
      }
      for (int index = 0; index <= config.SMALL_BIN_COUNT; ++index)
        heap._binsSmall[index] = index <= 0 ? 0 : heap.CalculateBin(index * 8);
      return heap;
    }

    public unsafe void Free(Ptr ptr) => this.Free(this.VoidUnchecked(ptr));

    public unsafe void Free(void* ptr)
    {
      Heap.Page* p = (Heap.Page*) ((UIntPtr) this._pages + (UIntPtr) (((byte*) ptr - this._memory >> this._config.PAGE_SHIFT) * (long) sizeof (Heap.Page)));
      Heap.Block* ptr1 = (Heap.Block*) ptr;
      ptr1->Next = p->BlockFree;
      p->BlockFree = this.VoidUnchecked((void*) ptr1);
      --p->Allocated;
      if (p->Allocated != 0)
        return;
      this.PageFree(p);
    }

    private unsafe bool BlockIsInFreeList(Heap.Page* p, Heap.Block* block)
    {
      Ptr ptr = this.VoidUnchecked((void*) block);
      for (Ptr p1 = p->BlockFree; p1.Offset != 0; p1 = ((Heap.Block*) this.VoidUnchecked(p1))->Next)
      {
        if (p1.Offset == ptr.Offset)
          return true;
      }
      return false;
    }

    public static void Reset() => Heap.Group.Bins._bins = (Heap.Bin[]) null;

    private unsafe Heap.Block* PageNextBlock(Heap.Page* p, int size, bool allowFull)
    {
      if (p->BlockFree.Offset == 0)
        return (Heap.Block*) null;
      Heap.Block* blockPtr = (Heap.Block*) this.VoidUnchecked(p->BlockFree);
      p->BlockFree = blockPtr->Next;
      ++p->Allocated;
      return blockPtr;
    }

    private unsafe Heap.Block* PageNextBlockMustSucceed(Heap.Page* p, int size, bool allowFull) => this.PageNextBlock(p, size, allowFull);

    private unsafe void PageForceFree()
    {
      for (int metaDataPages = this._offsets.MetaDataPages; metaDataPages < this._config.PAGE_COUNT; ++metaDataPages)
      {
        if (this._pages[metaDataPages].Allocated == 0)
          this.PageFree(this._pages + metaDataPages);
      }
    }

    private unsafe Heap.Page* PageAllocate(int bin)
    {
      if (this._data->PagesFree.Offset == 0)
      {
        this.PageForceFree();
        if (this._data->PagesFree.Offset == 0)
          return (Heap.Page*) null;
      }
      Heap.Page* p = (Heap.Page*) this.VoidUnchecked(this._data->PagesFree);
      Heap.Bin* b = this._bins + bin;
      p->Bin = bin;
      p->BlockSize = b->BlockSize;
      p->Reserved = this._config.PAGE_SIZE / p->BlockSize;
      this._data->PagesFree = p->Next;
      --this._data->PagesFreeCount;
      Native.Utils.Clear((void*) this.PageMemoryStart(p->Index), this._config.PAGE_SIZE);
      this.BinInsertFirst(b, p);
      this.PageExpand(p);
      return p;
    }

    private unsafe byte* PageMemoryStart(int index) => this._memory + index * this._config.PAGE_SIZE;

    private unsafe void PageExpand(Heap.Page* p)
    {
      int num1 = 4096 / p->BlockSize;
      int num2 = p->Reserved - p->Committed;
      if (num2 > num1)
        num2 = num1;
      if (num2 <= 0)
        num2 = 1;
      byte* numPtr = this.PageMemoryStart(p->Index);
      for (int index = 0; index < num2; ++index)
      {
        int num3 = p->Committed + index;
        Heap.Block* ptr = (Heap.Block*) (numPtr + num3 * p->BlockSize);
        ptr->Next = p->BlockFree;
        p->BlockFree = this.VoidUnchecked((void*) ptr);
      }
      p->Committed += num2;
    }

    private unsafe void PageMoveToFullBin(Heap.Page* p, int bin)
    {
      p->Flags |= Heap.PageFlags.Full;
      this.BinRemove(this._bins + bin, p);
      this.BinInsertLast(this._binsFull + bin, p);
    }

    private unsafe void PageMoveToAvailableBin(Heap.Page* p, int bin)
    {
      p->Flags &= ~Heap.PageFlags.Full;
      this.BinRemove(this._binsFull + bin, p);
      this.BinInsertFirst(this._bins + bin, p);
    }

    private unsafe void PageFree(Heap.Page* p)
    {
      if ((p->Flags & Heap.PageFlags.Full) == Heap.PageFlags.Full)
        this.BinRemove(this._binsFull + p->Bin, p);
      else
        this.BinRemove(this._bins + p->Bin, p);
      p->Next = this._data->PagesFree;
      p->Bin = -1;
      p->Reserved = 0;
      p->Committed = 0;
      p->Allocated = 0;
      p->BlockSize = 0;
      p->BlockFree = new Ptr();
      p->Prev = new Ptr();
      p->Flags &= ~Heap.PageFlags.Full;
      this._data->PagesFree = this.VoidUnchecked((void*) p);
      ++this._data->PagesFreeCount;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public unsafe Ptr Void(void* ptr)
    {
      Ptr ptr1;
      ptr1.Offset = (int) ((byte*) ptr - this._memory);
      return ptr1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public unsafe void* Void(Ptr p) => (void*) (this._memory + p.Offset);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public unsafe T* Void<T>(Ptr p) where T : unmanaged => (T*) (this._memory + p.Offset);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    internal unsafe void* VoidUnchecked(Ptr p) => (void*) (this._memory + p.Offset);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    internal unsafe Ptr VoidUnchecked(void* ptr)
    {
      Ptr ptr1;
      ptr1.Offset = (int) ((byte*) ptr - this._memory);
      return ptr1;
    }

    private unsafe int CalculateBin(int size)
    {
      int wordSize = Heap.CalculateWordSize(size);
      if (wordSize <= 8)
        return wordSize;
      int v = wordSize - 1;
      int num = Heap.BitScanReverse((uint) v, this._debruijnTable);
      return (num << 2) + (v >> num - 2 & 3) - 3;
    }

    private static int CalculateWordSize(int size) => (size + 7) / 8;

    private static unsafe int BitScanReverse(uint v, byte* debruijnTable)
    {
      v |= v >> 1;
      v |= v >> 2;
      v |= v >> 4;
      v |= v >> 8;
      v |= v >> 16;
      return (int) debruijnTable[v * 130329821U >> 27];
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct AllocationTracker
    {
      public const int SIZE = 8;
      private const int DefaultCapacity = 8;
      [FieldOffset(0)]
      private ushort _trackAllocCount;
      [FieldOffset(2)]
      private ushort _trackAllocCapacity;
      [FramePrinter.PtrBuffer(typeof (Ptr), "_trackAllocCount")]
      [FieldOffset(4)]
      private Ptr _trackAllocPtr;

      public unsafe void TrackAllocation(FrameBase f, Ptr ptr) => this.EnsureBufferCapacity(f)[this._trackAllocCount++] = ptr;

      public unsafe void FreeAllTracked(FrameBase f)
      {
        if (this._trackAllocCount == (ushort) 0)
          return;
        Ptr* ptrPtr = (Ptr*) f.Heap->Void(this._trackAllocPtr);
        int num = 0;
        while (num < (int) this._trackAllocCount)
        {
          f.Heap->Free(*ptrPtr);
          ++num;
          ++ptrPtr;
        }
        this._trackAllocCount = (ushort) 0;
      }

      private unsafe Ptr* EnsureBufferCapacity(FrameBase f)
      {
        if ((int) this._trackAllocCount == (int) this._trackAllocCapacity)
        {
          if (this._trackAllocPtr.Offset == 0)
          {
            this._trackAllocCapacity = (ushort) 8;
            this._trackAllocPtr = f.Heap->AllocateAndClearPtr(sizeof (Ptr) * 8);
          }
          else
          {
            Ptr* buffer = (Ptr*) f.Heap->Void(this._trackAllocPtr);
            Ptr* ptr = f.Heap->ExpandArray<Ptr>(buffer, (int) this._trackAllocCapacity, (int) this._trackAllocCapacity * 2);
            this._trackAllocCapacity *= (ushort) 2;
            this._trackAllocPtr = f.Heap->Void((void*) ptr);
          }
        }
        return (Ptr*) f.Heap->Void(this._trackAllocPtr);
      }

      public static unsafe void Serialize(void* ptr, FrameSerializer serializer)
      {
        if (!serializer.Reading)
          return;
        Heap.AllocationTracker* allocationTrackerPtr = (Heap.AllocationTracker*) ptr;
        allocationTrackerPtr->_trackAllocPtr = new Ptr();
        allocationTrackerPtr->_trackAllocCapacity = (ushort) 0;
        allocationTrackerPtr->_trackAllocCount = (ushort) 0;
      }

      public unsafe void Dispose(FrameBase f)
      {
        if (this._trackAllocCapacity == (ushort) 0)
          return;
        f.Heap->Free(this._trackAllocPtr);
        this._trackAllocCount = (ushort) 0;
        this._trackAllocPtr = new Ptr();
      }
    }

    internal struct Group
    {
      private unsafe Heap* _heaps;
      private int _heapsCount;
      private Heap.Config _config;
      private Heap.Offsets _offsets;
      private unsafe void* _heapsMemory;
      private unsafe byte* _heapsStart;
      private unsafe byte* _debrujinTable;

      public Heap.Config GetConfig() => this._config;

      public unsafe Heap* GetHeap(int index)
      {
        if ((uint) index >= (uint) this._heapsCount)
          throw new IndexOutOfRangeException();
        return this._heaps + index;
      }

      public unsafe void Release(Heap* heap) => *heap = Heap.Initialize(heap->_memory, this._debrujinTable, this._config, this._offsets, Heap.Group.Bins.Default());

      public unsafe Heap* Reserve()
      {
        for (int index = 0; index < this._config.HEAP_COUNT; ++index)
        {
          Heap* heap = this.GetHeap(index);
          if (heap->_reserved == (byte) 0)
          {
            heap->_reserved = (byte) 1;
            return heap;
          }
        }
        throw new InvalidOperationException(string.Format("All {0} heaps are already reserved", (object) this._config.HEAP_COUNT));
      }

      public static unsafe void Free(Heap.Group* group, Native.Allocator allocator)
      {
        if ((IntPtr) group == IntPtr.Zero)
          throw new NullReferenceException();
        if ((IntPtr) group->_heapsMemory == IntPtr.Zero)
          throw new NullReferenceException();
        allocator.Free(group->_heapsMemory);
        *group = new Heap.Group();
        allocator.Free((void*) group);
      }

      public static unsafe Heap.Group* Allocate(Heap.Config config, Native.Allocator allocator)
      {
        int alignment1 = Native.RoundUpToAlignment(sizeof (Heap.Group), 8);
        int alignment2 = Native.RoundUpToAlignment(sizeof (Heap) * config.HEAP_COUNT, 8);
        int alignment3 = Native.RoundUpToAlignment(32, 8);
        int count = alignment1 + alignment2 + alignment3;
        byte* numPtr = (byte*) allocator.AllocAndClear(count, 8);
        Heap.Group* group = (Heap.Group*) numPtr;
        group->_heaps = (Heap*) (numPtr + alignment1);
        group->_debrujinTable = numPtr + (alignment1 + alignment2);
        group->_heapsCount = config.HEAP_COUNT;
        group->_config = config;
        group->_offsets = Heap.Group.CalculateMetaOffsets(group->_config);
        Heap.Group.InitDeBruijnTable(group->_debrujinTable);
        Heap.Group.InitHeaps(group, allocator);
        return group;
      }

      private static unsafe void InitHeaps(Heap.Group* group, Native.Allocator allocator)
      {
        long num1 = (long) group->_heapsCount * (long) group->_config.HEAP_SIZE + (long) group->_config.HEAP_SIZE;
        if (num1 > (long) uint.MaxValue)
          throw new ArgumentOutOfRangeException("totalSize", (object) num1, string.Format("Total frame heap memory size in bytes must be smaller than or equal to uint.MaxValue ({0}).", (object) uint.MaxValue));
        Heap.Bin[] defaultBins = Heap.Group.Bins.Default();
        group->_heapsMemory = allocator.AllocAndClear((int) num1, 8);
        long num2 = (long) group->_heapsMemory % (long) group->_config.PAGE_SIZE;
        group->_heapsStart = (byte*) ((IntPtr) group->_heapsMemory + (IntPtr) ((long) group->_config.PAGE_SIZE - num2));
        for (int index = 0; index < group->_heapsCount; ++index)
        {
          byte* memory = group->_heapsStart + (long) index * (long) group->_config.HEAP_SIZE;
          group->_heaps[index] = Heap.Initialize(memory, group->_debrujinTable, group->_config, group->_offsets, defaultBins);
        }
      }

      private static Heap.Offsets CalculateMetaOffsets(Heap.Config config)
      {
        int alignment1 = Native.RoundUpToAlignment(sizeof (Heap.Data), 4);
        int alignment2 = Native.RoundUpToAlignment(sizeof (Heap.Bin) * 49, 4);
        int alignment3 = Native.RoundUpToAlignment(sizeof (Heap.Bin) * 49, 4);
        int alignment4 = Native.RoundUpToAlignment(4 * config.SMALL_BIN_ARRAY_SIZE, 4);
        int alignment5 = Native.RoundUpToAlignment(sizeof (Heap.Page) * config.PAGE_COUNT, 4);
        Heap.Offsets metaOffsets;
        metaOffsets.DataOffset = 0;
        metaOffsets.BinsOffset = metaOffsets.DataOffset + alignment1;
        metaOffsets.BinsFullOffset = metaOffsets.BinsOffset + alignment2;
        metaOffsets.BinsSmallOffset = metaOffsets.BinsFullOffset + alignment3;
        metaOffsets.PagesOffset = metaOffsets.BinsSmallOffset + alignment4;
        metaOffsets.MetaDataPages = (metaOffsets.PagesOffset + alignment5 + (config.PAGE_SIZE - 1)) / config.PAGE_SIZE;
        return metaOffsets;
      }

      private static unsafe void InitDeBruijnTable(byte* table)
      {
        byte[] numArray = new byte[32]
        {
          (byte) 0,
          (byte) 9,
          (byte) 1,
          (byte) 10,
          (byte) 13,
          (byte) 21,
          (byte) 2,
          (byte) 29,
          (byte) 11,
          (byte) 14,
          (byte) 16,
          (byte) 18,
          (byte) 22,
          (byte) 25,
          (byte) 3,
          (byte) 30,
          (byte) 8,
          (byte) 12,
          (byte) 20,
          (byte) 28,
          (byte) 15,
          (byte) 17,
          (byte) 24,
          (byte) 7,
          (byte) 19,
          (byte) 27,
          (byte) 23,
          (byte) 6,
          (byte) 26,
          (byte) 5,
          (byte) 4,
          (byte) 31
        };
        for (int index = 0; index < 32; ++index)
          table[index] = numArray[index];
      }

      internal static class Bins
      {
        internal static Heap.Bin[] _bins;

        public static Heap.Bin[] Default()
        {
          if (Heap.Group.Bins._bins == null)
            Heap.Group.Bins._bins = new Heap.Bin[49]
            {
              new Heap.Bin(0),
              new Heap.Bin(1),
              new Heap.Bin(2),
              new Heap.Bin(3),
              new Heap.Bin(4),
              new Heap.Bin(5),
              new Heap.Bin(6),
              new Heap.Bin(7),
              new Heap.Bin(8),
              new Heap.Bin(10),
              new Heap.Bin(12),
              new Heap.Bin(14),
              new Heap.Bin(16),
              new Heap.Bin(20),
              new Heap.Bin(24),
              new Heap.Bin(28),
              new Heap.Bin(32),
              new Heap.Bin(40),
              new Heap.Bin(48),
              new Heap.Bin(56),
              new Heap.Bin(64),
              new Heap.Bin(80),
              new Heap.Bin(96),
              new Heap.Bin(112),
              new Heap.Bin(128),
              new Heap.Bin(160),
              new Heap.Bin(192),
              new Heap.Bin(224),
              new Heap.Bin(256),
              new Heap.Bin(320),
              new Heap.Bin(384),
              new Heap.Bin(448),
              new Heap.Bin(512),
              new Heap.Bin(640),
              new Heap.Bin(768),
              new Heap.Bin(896),
              new Heap.Bin(1024),
              new Heap.Bin(1280),
              new Heap.Bin(1536),
              new Heap.Bin(1792),
              new Heap.Bin(2048),
              new Heap.Bin(2560),
              new Heap.Bin(3072),
              new Heap.Bin(3584),
              new Heap.Bin(4096),
              new Heap.Bin(5120),
              new Heap.Bin(6144),
              new Heap.Bin(7168),
              new Heap.Bin(8192)
            };
          return Heap.Group.Bins._bins;
        }
      }
    }

    public struct Config
    {
      public const int PAGE_SHIFT_SIZE_2KB = 11;
      public const int PAGE_SHIFT_SIZE_4KB = 12;
      public const int PAGE_SHIFT_SIZE_8KB = 13;
      public const int PAGE_SHIFT_SIZE_16KB = 14;
      public const int PAGE_SHIFT_SIZE_32KB = 15;
      public const int PAGE_SHIFT_SIZE_64KB = 16;
      public readonly int PAGE_SHIFT;
      public readonly int PAGE_COUNT;
      public readonly int PAGE_SIZE;
      public readonly int HEAP_SIZE;
      public readonly int HEAP_COUNT;
      public readonly int SMALL_BIN_SIZE;
      public readonly int SMALL_BIN_COUNT;
      public readonly int SMALL_BIN_ARRAY_SIZE;

      public Config(int pageShift, int pageCount, int heapCount)
      {
        this.PAGE_SHIFT = pageShift;
        this.PAGE_COUNT = pageCount;
        this.PAGE_SIZE = 1 << this.PAGE_SHIFT;
        this.HEAP_SIZE = this.PAGE_SIZE * this.PAGE_COUNT;
        this.HEAP_COUNT = heapCount;
        this.SMALL_BIN_SIZE = this.PAGE_SIZE / (1 << this.PAGE_SHIFT - 9);
        this.SMALL_BIN_COUNT = this.SMALL_BIN_SIZE / 8;
        this.SMALL_BIN_ARRAY_SIZE = this.SMALL_BIN_COUNT + 1;
      }

      public static Heap.Config Default(int heapCount) => new Heap.Config(13, 256, heapCount);

      public bool Equals(Heap.Config other) => this.PAGE_SHIFT == other.PAGE_SHIFT && this.PAGE_COUNT == other.PAGE_COUNT && this.PAGE_SIZE == other.PAGE_SIZE && this.HEAP_SIZE == other.HEAP_SIZE && this.SMALL_BIN_SIZE == other.SMALL_BIN_SIZE && this.SMALL_BIN_COUNT == other.SMALL_BIN_COUNT && this.SMALL_BIN_ARRAY_SIZE == other.SMALL_BIN_ARRAY_SIZE;

      public override bool Equals(object obj) => obj is Heap.Config other && this.Equals(other);

      public override int GetHashCode() => (((((this.PAGE_SHIFT * 397 ^ this.PAGE_COUNT) * 397 ^ this.PAGE_SIZE) * 397 ^ this.HEAP_SIZE) * 397 ^ this.SMALL_BIN_SIZE) * 397 ^ this.SMALL_BIN_COUNT) * 397 ^ this.SMALL_BIN_ARRAY_SIZE;

      public static bool operator ==(Heap.Config a, Heap.Config b) => a.Equals(b);

      public static bool operator !=(Heap.Config a, Heap.Config b) => !a.Equals(b);

      public override string ToString() => string.Format("[{0}: {1}, {2}: {3}, {4}: {5}, {6}: {7}, {8}: {9}, {10}: {11}, {12}: {13}]", (object) "PAGE_SHIFT", (object) this.PAGE_SHIFT, (object) "PAGE_COUNT", (object) this.PAGE_COUNT, (object) "PAGE_SIZE", (object) this.PAGE_SIZE, (object) "HEAP_SIZE", (object) this.HEAP_SIZE, (object) "SMALL_BIN_SIZE", (object) this.SMALL_BIN_SIZE, (object) "SMALL_BIN_COUNT", (object) this.SMALL_BIN_COUNT, (object) "SMALL_BIN_ARRAY_SIZE", (object) this.SMALL_BIN_ARRAY_SIZE);
    }

    private struct Offsets
    {
      public int DataOffset;
      public int BinsOffset;
      public int BinsFullOffset;
      public int BinsSmallOffset;
      public int PagesOffset;
      public int MetaDataPages;

      public override string ToString() => string.Format("[{0}: {1}, {2}: {3}, {4}: {5}, {6}: {7}, {8}: {9}, {10}: {11}]", (object) "DataOffset", (object) this.DataOffset, (object) "BinsOffset", (object) this.BinsOffset, (object) "BinsFullOffset", (object) this.BinsFullOffset, (object) "BinsSmallOffset", (object) this.BinsSmallOffset, (object) "PagesOffset", (object) this.PagesOffset, (object) "MetaDataPages", (object) this.MetaDataPages);
    }

    public struct Stats
    {
      public int TotalMemory;
      public int TotalPages;
      public int PagesFree;
      public int PagesFull;
      public int PagesUsed;
      public int BytesAllocated;
      public int BytesReserved;
      public int BytesCommited;
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct Data
    {
      public const int FIELDS = 2;
      public const int SIZE = 8;
      [FieldOffset(0)]
      public Ptr PagesFree;
      [FieldOffset(4)]
      public int PagesFreeCount;

      public override string ToString() => string.Format("[{0}: {1}, {2}: {3}]", (object) "PagesFree", (object) this.PagesFree, (object) "PagesFreeCount", (object) this.PagesFreeCount);
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct Bin
    {
      public const int FIELDS = 4;
      public const int SIZE = 16;
      [FieldOffset(0)]
      public Ptr Head;
      [FieldOffset(4)]
      public Ptr Tail;
      [FieldOffset(8)]
      public int BlockSize;
      [FieldOffset(12)]
      public int PageCount;

      public Bin(int words)
      {
        this.Head = new Ptr();
        this.Tail = new Ptr();
        this.PageCount = 0;
        this.BlockSize = words * 8;
      }

      public override string ToString() => string.Format("[{0}: {1}, {2}: {3}, {4}: {5}, {6}: {7}]", (object) "Head", (object) this.Head, (object) "Tail", (object) this.Tail, (object) "BlockSize", (object) this.BlockSize, (object) "PageCount", (object) this.PageCount);
    }

    [Flags]
    internal enum PageFlags
    {
      Full = 1,
      Meta = 2,
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct Page
    {
      public const int FIELDS = 10;
      public const int SIZE = 40;
      [FieldOffset(0)]
      public int Bin;
      [FieldOffset(4)]
      public int Index;
      [FieldOffset(8)]
      public Heap.PageFlags Flags;
      [FieldOffset(12)]
      public int Reserved;
      [FieldOffset(16)]
      public int Committed;
      [FieldOffset(20)]
      public int Allocated;
      [FieldOffset(24)]
      public int BlockSize;
      [FieldOffset(28)]
      public Ptr BlockFree;
      [FieldOffset(32)]
      public Ptr Prev;
      [FieldOffset(36)]
      public Ptr Next;

      public override string ToString() => string.Format("[{0}: ({1}), {2}: {3}, {4}: {5}, {6}: {7}, {8}: {9}, {10}: {11}, {12}: {13}, {14}: {15}, {16}: {17}, {18}: {19}]", (object) "Flags", (object) this.Flags, (object) "Index", (object) this.Index, (object) "Bin", (object) this.Bin, (object) "Reserved", (object) this.Reserved, (object) "Committed", (object) this.Committed, (object) "Allocated", (object) this.Allocated, (object) "BlockSize", (object) this.BlockSize, (object) "BlockFree", (object) this.BlockFree, (object) "Prev", (object) this.Prev, (object) "Next", (object) this.Next);
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct Block
    {
      public const int FIELDS = 1;
      public const int SIZE = 4;
      [FieldOffset(0)]
      public Ptr Next;
    }
  }
}